package example

import (
	"fmt"
	"reflect"
	"strings"
	"time"
)

type criteria struct {
	clause []string
	values []interface{}
}

func (c *criteria) AndIsNull(field string) Criteria {
	return c.addAndClause(field, KeywordIsNull, nil)
}

func (c *criteria) AndIsNotNull(field string) Criteria {
	return c.addAndClause(field, KeywordIsNotNull, nil)
}

func (c *criteria) AndEqualTo(field string, value interface{}) Criteria {
	return c.addAndClause(field, KeywordEqual, value)
}

func (c *criteria) AndNotEqualTo(field string, value interface{}) Criteria {
	return c.addAndClause(field, KeywordNotEqual, value)
}

func (c *criteria) AndGreaterThan(field string, value interface{}) Criteria {
	return c.addAndClause(field, KeywordGreater, value)
}

func (c *criteria) AndNotGreaterThan(field string, value interface{}) Criteria {
	return c.addAndClause(field, KeywordNotGreater, value)
}

func (c *criteria) AndLessThan(field string, value interface{}) Criteria {
	return c.addAndClause(field, KeywordLess, value)
}

func (c *criteria) AndNotLessThan(field string, value interface{}) Criteria {
	return c.addAndClause(field, KeywordNotLess, value)
}

func (c *criteria) AndIn(field string, list ...interface{}) Criteria {
	return c.addAndClause(field, KeywordIn, list...)
}

func (c *criteria) AndNotIn(field string, list ...interface{}) Criteria {
	return c.addAndClause(field, KeywordNotIn, list...)
}

func (c *criteria) AndLike(field string, value interface{}) Criteria {
	return c.addAndClause(field, KeywordLike, fmt.Sprintf("%%%v%%", value))
}

func (c *criteria) AndNotLike(field string, value interface{}) Criteria {
	return c.addAndClause(field, KeywordNotLike, fmt.Sprintf("%%%v%%", value))
}

func (c *criteria) AndBetween(field string, value1, value2 interface{}) Criteria {
	return c.addAndClause(field, KeywordBetween, value1, value2)
}

func (c *criteria) AndNotBetween(field string, value1, value2 interface{}) Criteria {
	return c.addAndClause(field, KeywordNotBetween, value1, value2)
}

// or
func (c *criteria) OrIsNull(field string) Criteria {
	return c.addOrClause(field, KeywordIsNull, nil)
}

func (c *criteria) OrIsNotNull(field string) Criteria {
	return c.addOrClause(field, KeywordIsNotNull, nil)
}

func (c *criteria) OrEqualTo(field string, value interface{}) Criteria {
	return c.addOrClause(field, KeywordEqual, value)
}

func (c *criteria) OrNotEqualTo(field string, value interface{}) Criteria {
	return c.addOrClause(field, KeywordNotEqual, value)
}

func (c *criteria) OrGreaterThan(field string, value interface{}) Criteria {
	return c.addOrClause(field, KeywordGreater, value)
}

func (c *criteria) OrNotGreaterThan(field string, value interface{}) Criteria {
	return c.addOrClause(field, KeywordNotGreater, value)
}

func (c *criteria) OrLessThan(field string, value interface{}) Criteria {
	return c.addOrClause(field, KeywordLess, value)
}

func (c *criteria) OrNotLessThan(field string, value interface{}) Criteria {
	return c.addOrClause(field, KeywordNotLess, value)
}

func (c *criteria) OrIn(field string, list ...interface{}) Criteria {
	return c.addOrClause(field, KeywordIn, list...)
}

func (c *criteria) OrNotIn(field string, list ...interface{}) Criteria {
	return c.addOrClause(field, KeywordNotIn, list...)
}

func (c *criteria) OrLike(field string, value interface{}) Criteria {
	return c.addOrClause(field, KeywordLike, fmt.Sprintf("%%%v%%", value))
}

func (c *criteria) OrNotLike(field string, value interface{}) Criteria {
	return c.addOrClause(field, KeywordNotLike, fmt.Sprintf("%%%v%%", value))
}

func (c *criteria) OrBetween(field string, value1, value2 interface{}) Criteria {
	return c.addOrClause(field, KeywordBetween, value1, value2)
}

func (c *criteria) OrNotBetween(field string, value1, value2 interface{}) Criteria {
	return c.addOrClause(field, KeywordNotBetween, value1, value2)
}

func (c *criteria) Raw() (string, []interface{}) {
	return strings.Join(c.clause, " "), c.values
}

func (c *criteria) addAndClause(field, condition string, value ...interface{}) Criteria {
	return c.addClause(KeywordAnd, field, condition, value...)
}

func (c *criteria) addOrClause(field, condition string, value ...interface{}) Criteria {
	return c.addClause(KeywordOr, field, condition, value...)
}

func (c *criteria) addClause(typ string, field, condition string, value ...interface{}) Criteria {
	if len(value) == 1 {
		if len(c.clause) == 0 {
			c.clause = append(c.clause, buildClause(field, condition, value[0]))
		} else {
			c.clause = append(c.clause, fmt.Sprintf("%s %s", typ, buildClause(field, condition, value[0])))
		}
		if value[0] == nil {
			return c
		}
	} else {
		c.clause = append(c.clause, buildClause(field, condition, value))
	}
	c.values = append(c.values, value...)
	return c
}

func buildClause(field, condition string, value interface{}) string {
	var fmtVal string
	if value != nil {
		t := reflect.TypeOf(value)
		switch t.Kind() {
		case reflect.Slice:
			v := reflect.ValueOf(value)
			arr := make([]string, v.Len())
			for i := 0; i < v.Len(); i++ {
				arr[i] = fmtValue(v.Index(i).Interface())
			}
			fmtVal = fmt.Sprintf("( %s )", strings.Join(arr, ", "))
		case reflect.Ptr:
			v := reflect.ValueOf(value)
			fmtVal = fmtValue(v.Elem().Interface())
		default:
			fmtVal = fmtValue(value)
		}
	}
	return fmt.Sprintf("`%s` %s %s", field, condition, fmtVal)
}

func fmtValue(value interface{}) string {
	var fmtVal string
	switch value.(type) {
	case string:
		fmtVal = PlaceholderString
	case time.Time:
		fmtVal = PlaceholderString
	default:
		fmtVal = PlaceholderNumeric
	}
	return fmtVal
}
